# EmbeddedClassMapping.java

    package org.codefilarete.stalactite.mapping;

    import java.util.Collection;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;
    import java.util.function.Function;
    import javax.annotation.Nullable;

    import org.codefilarete.reflection.Accessor;
    import org.codefilarete.reflection.Accessors;
    import org.codefilarete.reflection.Mutator;
    import org.codefilarete.reflection.PropertyAccessPoint;
    import org.codefilarete.reflection.PropertyMutator;
    import org.codefilarete.reflection.ReadWriteAccessorChain;
    import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
    import org.codefilarete.reflection.ValueAccessPointMap;
    import org.codefilarete.reflection.ValueAccessPointSet;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.result.ColumnedRow;
    import org.codefilarete.tool.Duo;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.KeepOrderMap;
    import org.codefilarete.tool.collection.KeepOrderSet;
    import org.codefilarete.tool.function.Converter;
    import org.codefilarete.tool.function.Predicates;

    import static org.codefilarete.tool.Nullable.nullable;

    /**
     * Persistence strategy for "embedded" bean (no identifier nor relation managed here) : straight mapping betwen some properties
     * of a class and some columns of a table.
     *
     * @author Guillaume Mary
     */
    public class EmbeddedClassMapping<C, T extends Table<T>> implements EmbeddedBeanMapping<C, T> {

    	private final Class<C> classToPersist;

    	private final T targetTable;

    	private final Map<ReadWritePropertyAccessPoint<C, ?>, Column<T, ?>> propertyToColumn;

    	// Could be a Map<Mutator....> if we could have a AccessorChain that can be a Mutator which is not currently the case
    	private final Map<PropertyMutator<C, ?>, Column<T, ?>> readonlyPropertyToColumn;

    	private final Set<Column<T, Object>> columns;

    	/** Acts as a cache of insertable properties, could be dynamically deduced from {@link #propertyToColumn} */
    	private final Map<Accessor<C, ?>, Column<T, ?>> insertableProperties;

    	/** Acts as a cache of updatable properties, could be dynamically deduced from {@link #propertyToColumn} */
    	private final Map<Accessor<C, ?>, Column<T, ?>> updatableProperties;

    	private final ToBeanRowTransformer<C> rowTransformer;

    	private DefaultValueDeterminer defaultValueDeterminer = new DefaultValueDeterminer() {};

    	/**
    	 * Columns and their value provider which are not officially mapped by a bean property.
    	 * Those are for insertion time.
    	 */
    	private final KeepOrderSet<ShadowColumnValueProvider<C, T>> shadowColumnsForInsert = new KeepOrderSet<>();

    	/**
    	 * Columns and their value provider which are not officially mapped by a bean property.
    	 * Those are for update time.
    	 */
    	private final KeepOrderSet<ShadowColumnValueProvider<C, T>> shadowColumnsForUpdate = new KeepOrderSet<>();

    	private final ValueAccessPointSet<C, PropertyAccessPoint<C, ?>> propertiesSetByConstructor = new ValueAccessPointSet<>();

    	private ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>> readConverters = new ValueAccessPointMap<>();

    	private ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>> writeConverters = new ValueAccessPointMap<>();

    	/**
    	 * Builds an embedded class mapping between its properties (as {@link PropertyAccessPoint}) and some {@link Column}s.
    	 * {@link Column}s are expected to be from same table, no strong control is made about that except generic type, caller must be aware of it.
    	 *
    	 * @param classToPersist the class to be persisted
    	 * @param targetTable the persisting table
    	 * @param propertyToColumn a mapping between Field and Column, expected to be coherent (fields of same class, column of same table)
    	 */
    	public EmbeddedClassMapping(Class<C> classToPersist, T targetTable, Map<? extends ReadWritePropertyAccessPoint<C, ?>, ? extends Column<T, ?>> propertyToColumn) {
    		this(classToPersist, targetTable, propertyToColumn, new HashMap<>(), null);
    	}

    	public EmbeddedClassMapping(Class<C> classToPersist,
    								T targetTable,
    								Map<? extends ReadWritePropertyAccessPoint<C, ?>, ? extends Column<T, ?>> propertiesMapping,
    								Map<? extends PropertyMutator<C, ?>, ? extends Column<T, ?>> readonlyPropertiesMapping,
    								@Nullable Function<ColumnedRow, C> beanFactory) {
    		this.classToPersist = classToPersist;
    		this.targetTable = targetTable;
    		this.propertyToColumn = new KeepOrderMap<>(propertiesMapping);

    		// computing read columns
    		this.readonlyPropertyToColumn = new KeepOrderMap<>(readonlyPropertiesMapping);
    		Map<Column<T, ?>, Mutator> columnToField = Iterables.map(propertiesMapping.entrySet(), Entry::getValue, Entry::getKey, KeepOrderMap::new);
    		readonlyPropertiesMapping.forEach((accessor, column) -> columnToField.put(column, accessor));
    		this.rowTransformer = new EmbeddedBeanRowTransformer(nullable(beanFactory).getOr(() -> row -> Reflections.newInstance(classToPersist)), (Map) columnToField);
    		this.columns = (Set<Column<T, Object>>) (Set) new KeepOrderSet<>(rowTransformer.getColumnToMember().keySet());

    		// computing insertable columns
    		this.insertableProperties = new KeepOrderMap<>();
    		this.propertyToColumn.forEach((accessor, column) -> {
    			// auto-incremented columns mustn't be inserted
    			if (!column.isAutoGenerated()) {
    				this.insertableProperties.put(accessor, column);
    			}
    		});

    		// computing updatable columns
    		this.updatableProperties = new KeepOrderMap<>();
    		Set<Column<T, Object>> columnsWithoutPrimaryKey = targetTable.getColumnsNoPrimaryKey();
    		// primary key columns are not updatable
    		this.propertyToColumn.forEach((accessor, column) -> {
    			if (columnsWithoutPrimaryKey.contains(column)) {
    				this.updatableProperties.put(accessor, column);
    			}
    		});
    	}

    	public Class<C> getClassToPersist() {
    		return classToPersist;
    	}

    	public T getTargetTable() {
    		return targetTable;
    	}

    	/**
    	 * @return an immutable {@link Map} of the configured mapping
    	 */
    	@Override
    	public Map<ReadWritePropertyAccessPoint<C, ?>, Column<T, ?>> getPropertyToColumn() {
    		return Collections.unmodifiableMap(propertyToColumn);
    	}

    	/**
    	 * @return an immutable {@link Map} of the configured readonly mapping
    	 */
    	@Override
    	public Map<PropertyMutator<C, ?>, Column<T, ?>> getReadonlyPropertyToColumn() {
    		return Collections.unmodifiableMap(readonlyPropertyToColumn);
    	}

    	@Override
    	public ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>> getReadConverters() {
    		return readConverters;
    	}

    	public void setReadConverters(ValueAccessPointMap<C, ? extends Converter<Object, Object>, PropertyAccessPoint<C, ?>> converters) {
    		this.readConverters = (ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>>) converters;
    	}

    	@Override
    	public ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>> getWriteConverters() {
    		return writeConverters;
    	}

    	public void setWriteConverters(ValueAccessPointMap<C, ? extends Converter<Object, Object>, PropertyAccessPoint<C, ?>> writeConverters) {
    		this.writeConverters = (ValueAccessPointMap<C, Converter<Object, Object>, PropertyAccessPoint<C, ?>>) writeConverters;
    	}

    	/**
    	 * Gives mapped columns (can be a subset of the target table)
    	 * @return target mapped columns
    	 */
    	@Override
    	public Set<Column<T, ?>> getColumns() {
    		return Collections.unmodifiableSet(columns);
    	}

    	public Set<Column<T, ?>> getInsertableColumns() {
    		return Collections.unmodifiableSet(new KeepOrderSet<>(insertableProperties.values()));
    	}

    	public Set<Column<T, ?>> getUpdatableColumns() {
    		return Collections.unmodifiableSet(new KeepOrderSet<>(updatableProperties.values()));
    	}

    	@Override
    	public RowTransformer<C> getRowTransformer() {
    		return rowTransformer;
    	}

    	/**
    	 * Changes current {@link DefaultValueDeterminer}
    	 *
    	 * @param defaultValueDeterminer a {@link DefaultValueDeterminer}
    	 */
    	public void setDefaultValueDeterminer(DefaultValueDeterminer defaultValueDeterminer) {
    		this.defaultValueDeterminer = defaultValueDeterminer;
    	}

    	@Override
    	public void addShadowColumnInsert(ShadowColumnValueProvider<C, T> valueProvider) {
    		shadowColumnsForInsert.add(valueProvider);
    	}

    	@Override
    	public void addShadowColumnUpdate(ShadowColumnValueProvider<C, T> valueProvider) {
    		shadowColumnsForUpdate.add(valueProvider);
    	}

    	@Override
    	public <O> void addShadowColumnSelect(Column<T, O> column) {
    		columns.add((Column<T, Object>) column);
    	}

    	Collection<ShadowColumnValueProvider<C, T>> getShadowColumnsForInsert() {
    		return Collections.unmodifiableCollection(shadowColumnsForInsert);
    	}

    	Collection<ShadowColumnValueProvider<C, T>> getShadowColumnsForUpdate() {
    		return Collections.unmodifiableCollection(shadowColumnsForUpdate);
    	}

    	@Override
    	public void addPropertySetByConstructor(PropertyAccessPoint<C, ?> accessor) {
    		this.propertiesSetByConstructor.add(accessor);
    	}

    	@Override
    	public Map<Column<T, ?>, Object> getInsertValues(C c) {
    		Map<Column<T, ?>, Object> result = new KeepOrderMap<>();
    		insertableProperties.forEach((accessor, column) -> {
    			Object value = accessor.get(c);
    			// applying data converter if specified
    			Converter<Object, Object> converter = writeConverters.get(accessor);
    			if (converter != null) {
    				value = converter.convert(value);
    			}
    			result.put(column, value);
    		});
    		shadowColumnsForInsert.forEach(shadowColumnValueProvider -> {
    			if (shadowColumnValueProvider.accept(c)) {
    				result.putAll(shadowColumnValueProvider.giveValue(c));
    			}
    		});
    		return result;
    	}

    	@Override
    	public Map<UpwhereColumn<T>, Object> getUpdateValues(C modified, C unmodified, boolean allColumns) {
    		Map<Column<T, ?>, Object> unmodifiedColumns = new KeepOrderMap<>();
    		// getting differences
    		Map<UpwhereColumn<T>, Object> modifiedFields = new KeepOrderMap<>();
    		this.updatableProperties.forEach((accessor, column) -> {
    			Object modifiedValue = accessor.get(modified);
    			// applying data converter if specified
    			Converter<Object, Object> converter = writeConverters.get(accessor);
    			if (converter != null) {
    				modifiedValue = converter.convert(modifiedValue);
    			}

    			Object unmodifiedValue = unmodified == null ? null : accessor.get(unmodified);
    			if (!Predicates.equalOrNull(modifiedValue, unmodifiedValue)
    					// OR is here to take cases where getUpdateValues(..) gets only "modified" parameter (such as for updateById) and
    					// some modified properties are null, without this OR such properties won't be updated (set to null)
    					// and, overall, if they are all null, modifiedFields is empty then causing a statement without values
    					|| unmodified == null) {
    				modifiedFields.put(new UpwhereColumn<>(column, true), modifiedValue);
    			} else {
    				unmodifiedColumns.put(column, modifiedValue);
    			}
    		});

    		shadowColumnsForUpdate.forEach(shadowColumnValueProvider -> {
    			if (shadowColumnValueProvider.accept(modified)) {
    				if (modified != null && unmodified == null) {
    					Map<Column<T, ?>, ?> modifiedValues = shadowColumnValueProvider.giveValue(modified);
    					modifiedValues.forEach((col, value) -> modifiedFields.put(new UpwhereColumn<>(col, true), value));
    				} else if (modified == null && unmodified != null) {
    					Set<Column<T, ?>> shadowColumns = shadowColumnValueProvider.getColumns();
    					shadowColumns.forEach(col -> modifiedFields.put(new UpwhereColumn<>(col, true), null));
    				} else if (modified != null && unmodified != null) {
    					Map<Column<T, ?>, ?> modifiedValues = shadowColumnValueProvider.giveValue(modified);
    					Map<Column<T, ?>, ?> unmodifiedValues = shadowColumnValueProvider.giveValue(unmodified);

    					Set<Column<T, ?>> shadowColumns = shadowColumnValueProvider.getColumns();
    					shadowColumns.forEach(col -> {
    						Object modifiedValue = modifiedValues.get(col);
    						if (!Predicates.equalOrNull(modifiedValue, unmodifiedValues.get(col))) {
    							modifiedFields.put(new UpwhereColumn<>(col, true), modifiedValue);
    						} else {
    							unmodifiedColumns.put(col, modifiedValue);
    						}
    					});
    				}
    			}
    		});

    		// adding complementary columns if necessary
    		if (!modifiedFields.isEmpty() && allColumns) {
    			for (Entry<Column<T, ?>, Object> unmodifiedField : unmodifiedColumns.entrySet()) {
    				modifiedFields.put(new UpwhereColumn<>(unmodifiedField.getKey(), true), unmodifiedField.getValue());
    			}
    		}
    		return modifiedFields;
    	}

    	@Override
    	public C transform(ColumnedRow row) {
    		// NB: please note that this transformer will determine intermediary bean instantiation through isDefaultValue(..)
    		return this.rowTransformer.transform(row);
    	}

    	/**
    	 * Transformer aimed at detecting if bean instance must be created according to row values (used in the select phase).
    	 * For example, if all column values are null, bean will not be created.
    	 */
    	private class EmbeddedBeanRowTransformer extends ToBeanRowTransformer<C> {

    		public EmbeddedBeanRowTransformer(Function<? extends ColumnedRow, C> beanFactory,
    										  Map<? extends Column<?, ?>, ? extends Mutator<C, Object>> columnToMember) {
    			super(beanFactory, columnToMember);
    		}

    		@Override
    		public void applyRowToBean(ColumnedRow values, C targetRowBean) {
    			// Algorithm is a bit complex due to embedded beans into this embedded one and the fact that we may not instantiate them
    			// if all of their attributes has default values in current row : because instantiation is done silently by AccessorChainMutator
    			// (the object that let us set embedded bean attributes) we have to check the need of their invocation before ... their invocation.
    			// Therefore, we keep a boolean indicating if their values are default ones (if true its means that bean shouldn't be instantiated)
    			// per accessor to this bean. Example : with the mappings "Person::getTimestamp, Timestamp::setCreationDate" and
    			// "Person::getTimestamp, Timestamp::setModificationDate", then we keep a boolean per "Person::getTimestamp" saying that if one of
    			// creationDate and modificationDate is not null then we should instantiate Timestamp, if both are null then not.
    			Map<Entry<Column<?, ?>, Mutator<C, Object>>, Object> beanValues = new HashMap<>();
    			Map<Accessor, MutableBoolean> valuesAreDefaultOnes = new HashMap<>();
    			for (Entry<Column<?, ?>, Mutator<C, Object>> columnFieldEntry : getColumnToMember().entrySet()) {
    				Object propertyValue = values.get(columnFieldEntry.getKey());
    				// applying data converter if specified
    				Converter<Object, Object> converter = readConverters.get(columnFieldEntry.getValue());
    				if (converter != null) {
    					propertyValue = converter.convert(propertyValue);
    				}
    				beanValues.put(columnFieldEntry, propertyValue);
    				if (columnFieldEntry.getValue() instanceof ReadWriteAccessorChain) {
    					MutableBoolean mutableBoolean = valuesAreDefaultOnes.computeIfAbsent(((ReadWriteAccessorChain) columnFieldEntry.getValue()).toChainWithoutLastAccessPoint(), k -> new MutableBoolean(true));
    					boolean valueIsDefault = EmbeddedClassMapping.this.defaultValueDeterminer.isDefaultValue(
    							new Duo<>(columnFieldEntry.getKey(), columnFieldEntry.getValue()), propertyValue);
    					mutableBoolean.and(valueIsDefault);
    				}
    			}
    			// we apply values only if one of them is not a default one, because if all values are default one there's no reason that we create the bean
    			beanValues.forEach((mapping, value) -> {
    				if (mapping.getValue() instanceof ReadWriteAccessorChain) {
    					boolean valueIsDefault = valuesAreDefaultOnes.get(((ReadWriteAccessorChain) mapping.getValue()).toChainWithoutLastAccessPoint()).value();
    					if (!valueIsDefault) {
    						applyValueToBean(targetRowBean, mapping, value);
    					}
    				} else {
    					applyValueToBean(targetRowBean, mapping, value);
    				}
    			});
    		}

    		@Override
    		protected void applyValueToBean(C targetRowBean, Entry<? extends Column, ? extends Mutator<C, Object>> columnFieldEntry, Object propertyValue) {
    			// we skip properties set by constructor
    			if (!propertiesSetByConstructor.contains(columnFieldEntry.getValue())) {
    				super.applyValueToBean(targetRowBean, columnFieldEntry, propertyValue);
    			}
    		}

    		private class MutableBoolean {

    			private boolean value;

    			private MutableBoolean(boolean value) {
    				this.value = value;
    			}

    			public boolean value() {
    				return value;
    			}

    			public void and(boolean otherValue) {
    				this.value &= otherValue;
    			}
    		}
    	}

    	/**
    	 * Small contract that helps to determine if a value is a default one for a bean property.
    	 * Aimed at deciding if embedded beans must be instanced or not according to row values (from {@link java.sql.ResultSet}
    	 * during the conversion phase
    	 */
    	public interface DefaultValueDeterminer {

    		/**
    		 * Default implementation considers null as a default value for non-primitive types, and default primitive type values as such for
    		 * primitive types (took in {@link Reflections#PRIMITIVE_DEFAULT_VALUES}).
    		 *
    		 * @param mappedProperty column and its mapped property (configured in the {@link EmbeddedClassMapping}).
    		 * 						 So one can use either the column or the accessor for fine-grained default value determination
    		 * @param value value coming from a JDBC {@link java.sql.ResultSet}, mapped by the couple {@link Column} + {@link Mutator}.
    		 * @return true if value is a default one for column/property
    		 */
    		default boolean isDefaultValue(Duo<Column, Mutator> mappedProperty, Object value) {
    			// we consider accessor type as more fine grained than Column one, hence we check default value with it
    			Class inputType = Accessors.giveInputType(mappedProperty.getRight());
    			return (!inputType.isPrimitive() && value == null)
    					|| (inputType.isPrimitive() && Reflections.PRIMITIVE_DEFAULT_VALUES.get(inputType) == value);
    		}

    	}
    }